/**
 * Event management
 * @module Ink.Dom.Event_1
 * @version 1
 */

Ink.createModule('Ink.Dom.Event', 1, [], function() {
    /* jshint
           asi:true,
           strict:false,
           laxcomma:true,
           eqeqeq:false,
           laxbreak:true,
           boss:true,
           curly:false,
           expr:true
           */

    /**
     * @namespace Ink.Dom.Event_1
     * @static
     */

    /*!
      * Bean - copyright (c) Jacob Thornton 2011-2012
      * https://github.com/fat/bean
      * MIT license
      */
    var bean = (function (name, context, definition) {
      return definition()
    })('bean', this, function (name, context) {
      name    = name    || 'bean'
      context = context || this

      var win            = window
        , old            = context[name]
        , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
        , nameRegex      = /\..*/
        , addEvent       = 'addEventListener'
        , removeEvent    = 'removeEventListener'
        , doc            = document || {}
        , root           = doc.documentElement || {}
        , W3C_MODEL      = root[addEvent]
        , eventSupport   = W3C_MODEL ? addEvent : 'attachEvent'
        , ONE            = {} // singleton for quick matching making add() do one()

        , slice          = Array.prototype.slice
        , str2arr        = function (s, d) { return s.split(d || ' ') }
        , isString       = function (o) { return typeof o == 'string' }
        , isFunction     = function (o) { return typeof o == 'function' }

          // events that we consider to be 'native', anything not in this list will
          // be treated as a custom event
        , standardNativeEvents =
            'click dblclick mouseup mousedown contextmenu '                  + // mouse buttons
            'mousewheel mousemultiwheel DOMMouseScroll '                     + // mouse wheel
            'mouseover mouseout mousemove selectstart selectend '            + // mouse movement
            'keydown keypress keyup '                                        + // keyboard
            'orientationchange '                                             + // mobile
            'focus blur change reset select submit '                         + // form elements
            'load unload beforeunload resize move DOMContentLoaded '         + // window
            'readystatechange message '                                      + // window
            'error abort scroll '                                              // misc
          // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
          // that doesn't actually exist, so make sure we only do these on newer browsers
        , w3cNativeEvents =
            'show '                                                          + // mouse buttons
            'input invalid '                                                 + // form elements
            'touchstart touchmove touchend touchcancel '                     + // touch
            'gesturestart gesturechange gestureend '                         + // gesture
            'textinput'                                                      + // TextEvent
            'readystatechange pageshow pagehide popstate '                   + // window
            'hashchange offline online '                                     + // window
            'afterprint beforeprint '                                        + // printing
            'dragstart dragenter dragover dragleave drag drop dragend '      + // dnd
            'loadstart progress suspend emptied stalled loadmetadata '       + // media
            'loadeddata canplay canplaythrough playing waiting seeking '     + // media
            'seeked ended durationchange timeupdate play pause ratechange '  + // media
            'volumechange cuechange '                                        + // media
            'checking noupdate downloading cached updateready obsolete '       // appcache

          // convert to a hash for quick lookups
        , nativeEvents = (function (hash, events, i) {
            for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
            return hash
          }({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))

          // custom events are events that we *fake*, they are not provided natively but
          // we can use native events to generate them
        , customEvents = (function () {
            var isAncestor = 'compareDocumentPosition' in root
                  ? function (element, container) {
                      return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
                    }
                  : 'contains' in root
                    ? function (element, container) {
                        container = container.nodeType === 9 || container === window ? root : container
                        return container !== element && container.contains(element)
                      }
                    : function (element, container) {
                        while (element = element.parentNode) if (element === container) return 1
                        return 0
                      }
              , check = function (event) {
                  var related = event.relatedTarget
                  return !related
                    ? related == null
                    : (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
                        && !isAncestor(related, this))
                }

            return {
                mouseenter: { base: 'mouseover', condition: check }
              , mouseleave: { base: 'mouseout', condition: check }
              , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
            }
          }())

          // we provide a consistent Event object across browsers by taking the actual DOM
          // event object and generating a new one from its properties.
        , Event = (function () {
                // a whitelist of properties (for different event types) tells us what to check for and copy
            var commonProps  = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
                  'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey '  +
                  'srcElement target timeStamp type view which propertyName')
              , mouseProps   = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer '      +
                  'fromElement offsetX offsetY pageX pageY screenX screenY toElement'))
              , mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
                  'axis')) // 'axis' is FF specific
              , keyProps     = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier '          +
                  'keyLocation location'))
              , textProps    = commonProps.concat(str2arr('data'))
              , touchProps   = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
              , messageProps = commonProps.concat(str2arr('data origin source'))
              , stateProps   = commonProps.concat(str2arr('state'))
              , overOutRegex = /over|out/
                // some event types need special handling and some need special properties, do that all here
              , typeFixers   = [
                    { // key events
                        reg: /key/i
                      , fix: function (event, newEvent) {
                          newEvent.keyCode = event.keyCode || event.which
                          return keyProps
                        }
                    }
                  , { // mouse events
                        reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
                      , fix: function (event, newEvent, type) {
                          newEvent.rightClick = event.which === 3 || event.button === 2
                          newEvent.pos = { x: 0, y: 0 }
                          if (event.pageX || event.pageY) {
                            newEvent.clientX = event.pageX
                            newEvent.clientY = event.pageY
                          } else if (event.clientX || event.clientY) {
                            newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
                            newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
                          }
                          if (overOutRegex.test(type)) {
                            newEvent.relatedTarget = event.relatedTarget
                              || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
                          }
                          return mouseProps
                        }
                    }
                  , { // mouse wheel events
                        reg: /mouse.*(wheel|scroll)/i
                      , fix: function () { return mouseWheelProps }
                    }
                  , { // TextEvent
                        reg: /^text/i
                      , fix: function () { return textProps }
                    }
                  , { // touch and gesture events
                        reg: /^touch|^gesture/i
                      , fix: function () { return touchProps }
                    }
                  , { // message events
                        reg: /^message$/i
                      , fix: function () { return messageProps }
                    }
                  , { // popstate events
                        reg: /^popstate$/i
                      , fix: function () { return stateProps }
                    }
                  , { // everything else
                        reg: /.*/
                      , fix: function () { return commonProps }
                    }
                ]
              , typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism

              , Event = function (event, element, isNative) {
                  if (!arguments.length) return
                  event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
                  this.originalEvent = event
                  this.isNative       = isNative
                  this.isBean         = true

                  if (!event) return

                  var type   = event.type
                    , target = event.target || event.srcElement
                    , i, l, p, props, fixer

                  this.target = target && target.nodeType === 3 ? target.parentNode : target

                  if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
                    fixer = typeFixerMap[type]
                    if (!fixer) { // haven't encountered this event type before, map a fixer function for it
                      for (i = 0, l = typeFixers.length; i < l; i++) {
                        if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
                          typeFixerMap[type] = fixer = typeFixers[i].fix
                          break
                        }
                      }
                    }

                    props = fixer(event, this, type)
                    for (i = props.length; i--;) {
                      if (!((p = props[i]) in this) && p in event) this[p] = event[p]
                    }
                  }
                }

            // preventDefault() and stopPropagation() are a consistent interface to those functions
            // on the DOM, stop() is an alias for both of them together
            Event.prototype.preventDefault = function () {
              if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
              else this.originalEvent.returnValue = false
            }
            Event.prototype.stopPropagation = function () {
              if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
              else this.originalEvent.cancelBubble = true
            }
            Event.prototype.stop = function () {
              this.preventDefault()
              this.stopPropagation()
              this.stopped = true
            }
            // stopImmediatePropagation() has to be handled internally because we manage the event list for
            // each element
            // note that originalElement may be a Bean#Event object in some situations
            Event.prototype.stopImmediatePropagation = function () {
              if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
              this.isImmediatePropagationStopped = function () { return true }
            }
            Event.prototype.isImmediatePropagationStopped = function () {
              return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
            }
            Event.prototype.clone = function (currentTarget) {
              //TODO: this is ripe for optimisation, new events are *expensive*
              // improving this will speed up delegated events
              var ne = new Event(this, this.element, this.isNative)
              ne.currentTarget = currentTarget
              return ne
            }

            return Event
          }())

          // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
        , targetElement = function (element, isNative) {
            return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
          }

          /**
            * Bean maintains an internal registry for event listeners. We don't touch elements, objects
            * or functions to identify them, instead we store everything in the registry.
            * Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
            */
        , RegEntry = (function () {
            // each handler is wrapped so we can handle delegation and custom events
            var wrappedHandler = function (element, fn, condition, args) {
                var call = function (event, eargs) {
                      return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
                    }
                  , findTarget = function (event, eventElement) {
                      return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
                    }
                  , handler = condition
                      ? function (event) {
                          var target = findTarget(event, this) // deleated event
                          if (condition.apply(target, arguments)) {
                            if (event) event.currentTarget = target
                            return call(event, arguments)
                          }
                        }
                      : function (event) {
                          if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
                          return call(event, arguments)
                        }
                handler.__beanDel = fn.__beanDel
                return handler
              }

            , RegEntry = function (element, type, handler, original, namespaces, args, root) {
                var customType     = customEvents[type]
                  , isNative

                if (type == 'unload') {
                  // self clean-up
                  handler = once(removeListener, element, type, handler, original)
                }

                if (customType) {
                  if (customType.condition) {
                    handler = wrappedHandler(element, handler, customType.condition, args)
                  }
                  type = customType.base || type
                }

                this.isNative      = isNative = nativeEvents[type] && !!element[eventSupport]
                this.customType    = !W3C_MODEL && !isNative && type
                this.element       = element
                this.type          = type
                this.original      = original
                this.namespaces    = namespaces
                this.eventType     = W3C_MODEL || isNative ? type : 'propertychange'
                this.target        = targetElement(element, isNative)
                this[eventSupport] = !!this.target[eventSupport]
                this.root          = root
                this.handler       = wrappedHandler(element, handler, null, args)
              }

            // given a list of namespaces, is our entry in any of them?
            RegEntry.prototype.inNamespaces = function (checkNamespaces) {
              var i, j, c = 0
              if (!checkNamespaces) return true
              if (!this.namespaces) return false
              for (i = checkNamespaces.length; i--;) {
                for (j = this.namespaces.length; j--;) {
                  if (checkNamespaces[i] == this.namespaces[j]) c++
                }
              }
              return checkNamespaces.length === c
            }

            // match by element, original fn (opt), handler fn (opt)
            RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
              return this.element === checkElement &&
                (!checkOriginal || this.original === checkOriginal) &&
                (!checkHandler || this.handler === checkHandler)
            }

            return RegEntry
          }())

        , registry = (function () {
            // our map stores arrays by event type, just because it's better than storing
            // everything in a single array.
            // uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
            // rootListeners so we can look them up fast
            var map = {}

              // generic functional search of our registry for matching listeners,
              // `fn` returns false to break out of the loop
              , forAll = function (element, type, original, handler, root, fn) {
                  var pfx = root ? 'r' : '$'
                  if (!type || type == '*') {
                    // search the whole registry
                    for (var t in map) {
                      if (t.charAt(0) == pfx) {
                        forAll(element, t.substr(1), original, handler, root, fn)
                      }
                    }
                  } else {
                    var i = 0, l, list = map[pfx + type], all = element == '*'
                    if (!list) return
                    for (l = list.length; i < l; i++) {
                      if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
                    }
                  }
                }

              , has = function (element, type, original, root) {
                  // we're not using forAll here simply because it's a bit slower and this
                  // needs to be fast
                  var i, list = map[(root ? 'r' : '$') + type]
                  if (list) {
                    for (i = list.length; i--;) {
                      if (!list[i].root && list[i].matches(element, original, null)) return true
                    }
                  }
                  return false
                }

              , get = function (element, type, original, root) {
                  var entries = []
                  forAll(element, type, original, null, root, function (entry) {
                    return entries.push(entry)
                  })
                  return entries
                }

              , put = function (entry) {
                  var has = !entry.root && !this.has(entry.element, entry.type, null, false)
                    , key = (entry.root ? 'r' : '$') + entry.type
                  ;(map[key] || (map[key] = [])).push(entry)
                  return has
                }

              , del = function (entry) {
                  forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
                    list.splice(i, 1)
                    entry.removed = true
                    if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
                    return false
                  })
                }

                // dump all entries, used for onunload
              , entries = function () {
                  var t, entries = []
                  for (t in map) {
                    if (t.charAt(0) == '$') entries = entries.concat(map[t])
                  }
                  return entries
                }

            return { has: has, get: get, put: put, del: del, entries: entries }
          }())

          // we need a selector engine for delegated events, use querySelectorAll if it exists
          // but for older browsers we need Qwery, Sizzle or similar
        , selectorEngine
        , setSelectorEngine = function (e) {
            if (!arguments.length) {
              selectorEngine = doc.querySelectorAll
                ? function (s, r) {
                    return r.querySelectorAll(s)
                  }
                : function () {
                    throw new Error('Bean: No selector engine installed') // eeek
                  }
            } else {
              selectorEngine = e
            }
          }

          // we attach this listener to each DOM event that we need to listen to, only once
          // per event type per DOM element
        , rootListener = function (event, type) {
            if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return

            var listeners = registry.get(this, type || event.type, null, false)
              , l = listeners.length
              , i = 0

            event = new Event(event, this, true)
            if (type) event.type = type

            // iterate through all handlers registered for this type, calling them unless they have
            // been removed by a previous handler or stopImmediatePropagation() has been called
            for (; i < l && !event.isImmediatePropagationStopped(); i++) {
              if (!listeners[i].removed) listeners[i].handler.call(this, event)
            }
          }

          // add and remove listeners to DOM elements
        , listener = W3C_MODEL
            ? function (element, type, add) {
                // new browsers
                element[add ? addEvent : removeEvent](type, rootListener, false)
              }
            : function (element, type, add, custom) {
                // IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
                // to simulate event bubbling etc.
                var entry
                if (add) {
                  registry.put(entry = new RegEntry(
                      element
                    , custom || type
                    , function (event) { // handler
                        rootListener.call(element, event, custom)
                      }
                    , rootListener
                    , null
                    , null
                    , true // is root
                  ))
                  if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
                  entry.target.attachEvent('on' + entry.eventType, entry.handler)
                } else {
                  entry = registry.get(element, custom || type, rootListener, true)[0]
                  if (entry) {
                    entry.target.detachEvent('on' + entry.eventType, entry.handler)
                    registry.del(entry)
                  }
                }
              }

        , once = function (rm, element, type, fn, originalFn) {
            // wrap the handler in a handler that does a remove as well
            return function () {
              fn.apply(this, arguments)
              rm(element, type, originalFn)
            }
          }

        , removeListener = function (element, orgType, handler, namespaces) {
            var type     = orgType && orgType.replace(nameRegex, '')
              , handlers = registry.get(element, type, null, false)
              , removed  = {}
              , i, l

            for (i = 0, l = handlers.length; i < l; i++) {
              if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
                // TODO: this is problematic, we have a registry.get() and registry.del() that
                // both do registry searches so we waste cycles doing this. Needs to be rolled into
                // a single registry.forAll(fn) that removes while finding, but the catch is that
                // we'll be splicing the arrays that we're iterating over. Needs extra tests to
                // make sure we don't screw it up. @rvagg
                registry.del(handlers[i])
                if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
                  removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
              }
            }
            // check each type/element for removed listeners and remove the rootListener where it's no longer needed
            for (i in removed) {
              if (!registry.has(element, removed[i].t, null, false)) {
                // last listener of this type, remove the rootListener
                listener(element, removed[i].t, false, removed[i].c)
              }
            }
          }

          // set up a delegate helper using the given selector, wrap the handler function
        , delegate = function (selector, fn) {
            //TODO: findTarget (therefore $) is called twice, once for match and once for
            // setting e.currentTarget, fix this so it's only needed once
            var findTarget = function (target, root) {
                  var i, array = isString(selector) ? selectorEngine(selector, root) : selector
                  for (; target && target !== root; target = target.parentNode) {
                    for (i = array.length; i--;) {
                      if (array[i] === target) return target
                    }
                  }
                }
              , handler = function (e) {
                  var match = findTarget(e.target, this)
                  if (match) fn.apply(match, arguments)
                }

            // __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
            handler.__beanDel = {
                ft       : findTarget // attach it here for customEvents to use too
              , selector : selector
            }
            return handler
          }

        , fireListener = W3C_MODEL ? function (isNative, type, element) {
            // modern browsers, do a proper dispatchEvent()
            var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
            evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
            element.dispatchEvent(evt)
          } : function (isNative, type, element) {
            // old browser use onpropertychange, just increment a custom property to trigger the event
            element = targetElement(element, isNative)
            isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
          }

          /**
            * Public API: off(), on(), add(), (remove()), one(), fire(), clone()
            */

          /**
            * off(element[, eventType(s)[, handler ]])
            */
        , off = function (element, typeSpec, fn) {
            var isTypeStr = isString(typeSpec)
              , k, type, namespaces, i

            if (isTypeStr && typeSpec.indexOf(' ') > 0) {
              // off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
              typeSpec = str2arr(typeSpec)
              for (i = typeSpec.length; i--;)
                off(element, typeSpec[i], fn)
              return element
            }

            type = isTypeStr && typeSpec.replace(nameRegex, '')
            if (type && customEvents[type]) type = customEvents[type].base

            if (!typeSpec || isTypeStr) {
              // off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
              if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
              removeListener(element, type, fn, namespaces)
            } else if (isFunction(typeSpec)) {
              // off(el, fn)
              removeListener(element, null, typeSpec)
            } else {
              // off(el, { t1: fn1, t2, fn2 })
              for (k in typeSpec) {
                if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
              }
            }

            return element
          }

          /**
            * on(element, eventType(s)[, selector], handler[, args ])
            */
        , on = function(element, events, selector, fn) {
            var originalFn, type, types, i, args, entry, first

            //TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
            if (selector === undefined && typeof events == 'object') {
              //TODO: this can't handle delegated events
              for (type in events) {
                if (events.hasOwnProperty(type)) {
                  on.call(this, element, type, events[type])
                }
              }
              return
            }

            if (!isFunction(selector)) {
              // delegated event
              originalFn = fn
              args       = slice.call(arguments, 4)
              fn         = delegate(selector, originalFn, selectorEngine)
            } else {
              args       = slice.call(arguments, 3)
              fn         = originalFn = selector
            }

            types = str2arr(events)

            // special case for one(), wrap in a self-removing handler
            if (this === ONE) {
              fn = once(off, element, events, fn, originalFn)
            }

            for (i = types.length; i--;) {
              // add new handler to the registry and check if it's the first for this element/type
              first = registry.put(entry = new RegEntry(
                  element
                , types[i].replace(nameRegex, '') // event type
                , fn
                , originalFn
                , str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
                , args
                , false // not root
              ))
              if (entry[eventSupport] && first) {
                // first event of this type on this element, add root listener
                listener(element, entry.eventType, true, entry.customType)
              }
            }

            return element
          }

          /**
            * add(element[, selector], eventType(s), handler[, args ])
            *
            * Deprecated: kept (for now) for backward-compatibility
            */
        , add = function (element, events, fn, delfn) {
            return on.apply(
                null
              , !isString(fn)
                  ? slice.call(arguments)
                  : [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
            )
          }

          /**
            * one(element, eventType(s)[, selector], handler[, args ])
            */
        , one = function () {
            return on.apply(ONE, arguments)
          }

          /**
            * fire(element, eventType(s)[, args ])
            *
            * The optional 'args' argument must be an array, if no 'args' argument is provided
            * then we can use the browser's DOM event system, otherwise we trigger handlers manually
            */
        , fire = function (element, type, args) {
            var types = str2arr(type)
              , i, j, l, names, handlers

            for (i = types.length; i--;) {
              type = types[i].replace(nameRegex, '')
              if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
              if (!names && !args && element[eventSupport]) {
                fireListener(nativeEvents[type], type, element)
              } else {
                // non-native event, either because of a namespace, arguments or a non DOM element
                // iterate over all listeners and manually 'fire'
                handlers = registry.get(element, type, null, false)
                args = [false].concat(args)
                for (j = 0, l = handlers.length; j < l; j++) {
                  if (handlers[j].inNamespaces(names)) {
                    handlers[j].handler.apply(element, args)
                  }
                }
              }
            }
            return element
          }

          /**
            * clone(dstElement, srcElement[, eventType ])
            *
            * TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
            */
        , clone = function (element, from, type) {
            var handlers = registry.get(from, type, null, false)
              , l = handlers.length
              , i = 0
              , args, beanDel

            for (; i < l; i++) {
              if (handlers[i].original) {
                args = [ element, handlers[i].type ]
                if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
                args.push(handlers[i].original)
                on.apply(null, args)
              }
            }
            return element
          }

        , bean = {
              'on'                : on
            , 'add'               : add
            , 'one'               : one
            , 'off'               : off
            , 'remove'            : off
            , 'clone'             : clone
            , 'fire'              : fire
            , 'Event'             : Event
            , 'setSelectorEngine' : setSelectorEngine
            , 'noConflict'        : function () {
                context[name] = old
                return this
              }
          }

      // for IE, clean up on unload to avoid leaks
      if (win.attachEvent) {
        var cleanup = function () {
          var i, entries = registry.entries()
          for (i in entries) {
            if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
          }
          win.detachEvent('onunload', cleanup)
          win.CollectGarbage && win.CollectGarbage()
        }
        win.attachEvent('onunload', cleanup)
      }

      // initialize selector engine to internal default (qSA or throw Error)
      setSelectorEngine(Ink.ss)

      return bean
    });

    /**
     * Keep this declaration here and off Bean as it extends the Event
     * object and some properties are readonly in strict mode
     */
    'use strict';

    var InkEvent = {

    KEY_BACKSPACE: 8,
    KEY_TAB:       9,
    KEY_RETURN:   13,
    KEY_ESC:      27,
    KEY_LEFT:     37,
    KEY_UP:       38,
    KEY_RIGHT:    39,
    KEY_DOWN:     40,
    KEY_DELETE:   46,
    KEY_HOME:     36,
    KEY_END:      35,
    KEY_PAGEUP:   33,
    KEY_PAGEDOWN: 34,
    KEY_INSERT:   45,
    
    /**
     * Creates a debounced version of a function.
     * Returns a function which calls `func`, waiting at least `wait` milliseconds between calls. This is useful for events such as `scroll` or `resize`, which can be triggered too many times per second, slowing down the browser with needless function calls.
     *
     * *note:* This does not delay the first function call to the function.
     *
     * @method throttle
     * @param {Function} func   Function to call. Arguments and context are both passed.
     * @param {Number} [wait]=0 Milliseconds to wait between calls.
     * @sample Ink_Dom_Event_1_throttle.html 
     **/
    throttle: function (func, wait) {
        wait = wait || 0;
        var lastCall = 0;  // Warning: This breaks on Jan 1st 1970 0:00
        var timeout;
        var throttled = function () {
            var now = +new Date();
            var timeDiff = now - lastCall;
            if (timeDiff >= wait) {
                lastCall = now;
                return func.apply(this, [].slice.call(arguments));
            } else {
                var that = this;
                var args = [].slice.call(arguments);
                if (!timeout) {
                    timeout = setTimeout(function () {
                        timeout = null;
                        return throttled.apply(that, args);
                    }, wait - timeDiff);
                }
            }
        };
        return throttled;
    },

    /**
     * Gets the event's target element.
     *
     * @method element
     * @param {Object} ev  Event object
     * @return {DOMNode} The target
     * @sample Ink_Dom_Event_1_element.html 
     */
    element: function(ev) {
        var node = ev.delegationTarget ||
            ev.target ||
            // IE stuff
            (ev.type === 'mouseout'   && ev.fromElement) ||
            (ev.type === 'mouseleave' && ev.fromElement) ||
            (ev.type === 'mouseover'  && ev.toElement) ||
            (ev.type === 'mouseenter' && ev.toElement) ||
            ev.srcElement ||
            null;
        return node && (node.nodeType === 3 || node.nodeType === 4) ? node.parentNode : node;
    },

    /**
     * Gets the event's related target element.
     *
     * @method relatedTarget
     * @param {Object} ev event object
     * @return {DOMNode} The related target
     * @sample Ink_Dom_Event_1_relatedTarget.html 
     */
    relatedTarget: function(ev){
        var node = ev.relatedTarget ||
            // IE stuff
            (ev.type === 'mouseout'   && ev.toElement) ||
            (ev.type === 'mouseleave' && ev.toElement) ||
            (ev.type === 'mouseover'  && ev.fromElement) ||
            (ev.type === 'mouseenter' && ev.fromElement) ||
            null;
        return node && (node.nodeType === 3 || node.nodeType === 4) ? node.parentNode : node;
    },

    /**
     * Find closest ancestor element by tag name related to the event target.
     * Navigate up the DOM tree, looking for a tag with the name `elmTagName`.
     *
     * If such tag is not found, `document` is returned.
     *
     * @method findElement
     * @param {Object}  ev              Event object
     * @param {String}  elmTagName      Tag name to find
     * @param {Boolean} [force]=false   Flag to skip returning `document` and to return `false` instead.
     * @return {DOMElement} the first element which matches given tag name or the document element if the wanted tag is not found
     * @sample Ink_Dom_Event_1_findElement.html 
     */
    findElement: function(ev, elmTagName, force)
    {
        var node = this.element(ev);
        while(true) {
            if(node.nodeName.toLowerCase() === elmTagName.toLowerCase()) {
                return node;
            } else {
                node = node.parentNode;
                if(!node) {
                    if(force) {
                        return false;
                    }
                    return document;
                }
                if(!node.parentNode){
                    if(force){ return false; }
                    return document;
                }
            }
        }
    },

    /**
     * Attaches an event to element
     *
     * @method observe
     * @param {DOMElement|String}  element      Element id or element
     * @param {String}             eventName    Event name
     * @param {Function}           callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture] Flag to change event listening from bubbling to capture.
     * @return {Function} The event handler used. Hang on to this if you want to `stopObserving` later.
     * @sample Ink_Dom_Event_1_observe.html 
     */
    observe: function(element, eventName, callBack, useCapture) {
        element = Ink.i(element);
        if(element) {
            if(element.addEventListener) {
                element.addEventListener(eventName, callBack, !!useCapture);
            } else {
                element.attachEvent('on' + eventName, (callBack = Ink.bind(callBack, element)));
            }
            return callBack;
        }
    },

    /**
     * Like observe, but listen to the event only once.
     *
     * @method observeOnce
     * @param {DOMElement|String}  element      Element id or element
     * @param {String}             eventName    Event name
     * @param {Function}           callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture] Flag to change event listening from bubbling to capture.
     * @return {Function} The event handler used. Hang on to this if you want to `stopObserving` later.
     * @sample Ink_Dom_Event_1_observeOnce.html 
     */
    observeOnce: function (element, eventName, callBack, useCapture) {
        var onceBack = function () {
            InkEvent.stopObserving(element, eventName, onceBack);
            return callBack();
        };
        return InkEvent.observe(element, eventName, onceBack, useCapture);
    },

    /**
     * Attaches an event to a selector or array of elements.
     *
     * @method observeMulti
     * @param {Array|String}        elements       
     * @param {String}              eventName    Event name
     * @param {Function}            callBack     Receives the event object as a parameter. If you're manually firing custom events, check it's eventName property to make sure you're handling the right event.
     * @param {Boolean}            [useCapture]  Flag change event listening from bubbling to capture.
     * @return {Function} The used callback.
     * @sample Ink_Dom_Event_1_observeMulti.html 
     */
    observeMulti: function (elements, eventName, callBack, useCapture) {
        if (typeof elements === 'string') {
            elements = Ink.ss(elements);
        } else if ( /* is an element */ elements && elements.nodeType === 1) {
            elements = [elements];
        }
        if (!elements[0]) { return false; }

        for (var i = 0, len = elements.length; i < len; i++) {
            this.observe(elements[i], eventName, callBack, useCapture);
        }
        return callBack;
    },

    /**
     * Observes an event on an element and its descendants matching the selector.
     *
     * Requires Ink.Dom.Selector if you need to use a selector.
     *
     * @method observeDelegated
     * @param {DOMElement|String} element   Element to observe.
     * @param {String}            eventName Event name to observe.
     * @param {String}            selector  Child element selector. When null, finds any element.
     * @param {Function}          callback  Callback to be called when the event is fired
     * @return {Function} The used callback, for ceasing to listen to the event later.
     * @sample Ink_Dom_Event_1_observeDelegated.html 
     **/
    observeDelegated: function (element, eventName, selector, callback) {
        return InkEvent.observe(element, eventName, function (event) {
            var fromElement = InkEvent.element(event);
            if (!fromElement || fromElement === element) { return; }

            var cursor = fromElement;

            // Go up the document tree until we hit the element itself.
            while (cursor !== element && cursor !== document && cursor) {
                if (Ink.Dom.Selector_1.matchesSelector(cursor, selector)) {
                    event.delegationTarget = cursor;
                    return callback(event);
                }
                cursor = cursor.parentNode;
            }
        });
    },

    /**
     * Removes an event attached to an element.
     *
     * @method stopObserving
     * @param {DOMElement|String}  element       Element id or element
     * @param {String}             eventName     Event name
     * @param {Function}           callBack      Callback function
     * @param {Boolean}            [useCapture]  Set to true if the event was being observed with useCapture set to true as well.
     * @sample Ink_Dom_Event_1_stopObserving.html 
     */
    stopObserving: function(element, eventName, callBack, useCapture) {
        element = Ink.i(element);

        if(element) {
            if(element.removeEventListener) {
                element.removeEventListener(eventName, callBack, !!useCapture);
            } else {
                element.detachEvent('on' + eventName, callBack);
            }
        }
    },

    /**
     * Stops event propagation and bubbling.
     *
     * @method stop
     * @param {Object} event  Event handle
     * @sample Ink_Dom_Event_1_stop.html 
     */
    stop: function(event)
    {
        if(event.cancelBubble !== null) {
            event.cancelBubble = true;
        }
        if(event.stopPropagation) {
            event.stopPropagation();
        }
        if(event.preventDefault) {
            event.preventDefault();
        }
        if(window.attachEvent) {
            event.returnValue = false;
        }
        if(event.cancel !== null) {
            event.cancel = true;
        }
    },

    /**
     * Stops event propagation.
     *
     * @method stopPropagation
     * @param {Object} event  Event handle
     * @sample Ink_Dom_Event_1_stopPropagation.html 
     */
    stopPropagation: function(event) {
        if(event.cancelBubble !== null) {
            event.cancelBubble = true;
        }
        if(event.stopPropagation) {
            event.stopPropagation();
        }
    },

    /**
     * Stops event default behaviour.
     *
     * @method stopDefault
     * @param {Object} event  Event handle
     * @sample Ink_Dom_Event_1_stopDefault.html 
     */
    stopDefault: function(event)
    {
        if(event.preventDefault) {
            event.preventDefault();
        }
        if(window.attachEvent) {
            event.returnValue = false;
        }
        if(event.cancel !== null) {
            event.cancel = true;
        }
    },

    /**
     * Gets the pointer's coordinates from the event object.
     *
     * @method pointer
     * @param {Object} ev Event object
     * @return {Object} An object with the mouse X and Y position
     * @sample Ink_Dom_Event_1_pointer.html 
     */
    pointer: function(ev)
    {
        return {
            x: this.pointerX(ev),
            y: this.pointerY(ev)
        };
    },

    /**
     * Gets the pointer's X coordinate.
     *
     * @method pointerX
     * @param {Object} ev Event object
     * @return {Number} Mouse X position
     */
    pointerX: function(ev)
    {
        return (ev.touches && ev.touches[0] && ev.touches[0].pageX) ||
            (ev.pageX) ||
            (ev.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
    },

    /**
     * Gets the pointer's Y coordinate.
     *
     * @method pointerY
     * @param {Object} ev Event object
     * @return {Number} Mouse Y position
     */
    pointerY: function(ev)
    {
        return (ev.touches && ev.touches[0] && ev.touches[0].pageY) ||
            (ev.pageY) ||
            (ev.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
    },

    /**
     * Checks if an event is a left click.
     *
     * @method isLeftClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a left click
     * @sample Ink_Dom_Event_1_isLeftClick.html 
     */
    isLeftClick: function(ev) {
        if (window.addEventListener) {
            if(ev.button === 0){
                return true;
            } else if(ev.type === 'touchend' && ev.button === null){
                // [todo] do the above check for pointerEvents too
                return true;
            }
        }
        else {
            if(ev.button === 1){ return true; }
        }
        return false;
    },

    /**
     * Checks if an event is a right click.
     *
     * @method isRightClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a right click
     * @sample Ink_Dom_Event_1_isRightClick.html 
     */
    isRightClick: function(ev) {
        return (ev.button === 2);
    },

    /**
     * Checks if an event is a middle click.
     *
     * @method isMiddleClick
     * @param {Object} ev  Event object
     * @return {Boolean} True if the event is a middle click
     * @sample Ink_Dom_Event_1_isMiddleClick.html 
     */
    isMiddleClick: function(ev) {
        if (window.addEventListener) {
            return (ev.button === 1);
        }
        else {
            return (ev.button === 4);
        }
        return false;
    },

    /**
     * Gets character from an event.
     *
     * @method getCharFromKeyboardEvent
     * @param {Object}   event           Keyboard event
     * @param {Boolean}  [changeCasing]  If true uppercases, if false lowercases, otherwise keeps casing
     * @return {String} Character representation of pressed key combination
     * @sample Ink_Dom_Event_1_getCharFromKeyboardEvent.html 
     */
    getCharFromKeyboardEvent: function(event, changeCasing) {
        var k = event.keyCode;
        var c = String.fromCharCode(k);

        var shiftOn = event.shiftKey;
        if (k >= 65 && k <= 90) {   // A-Z
            if (typeof changeCasing === 'boolean') {
                shiftOn = changeCasing;
            }
            return (shiftOn) ? c : c.toLowerCase();
        }
        else if (k >= 96 && k <= 105) { // numpad digits
            return String.fromCharCode( 48 + (k-96) );
        }
        switch (k) {
            case 109:   case 189:   return '-';
            case 107:   case 187:   return '+';
        }
        return c;
    },

    debug: function(){}
};

/**
 * Lets you attach event listeners to both elements and objects.
 * http://github.com/fat/bean#on
 *
 * @method on
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to listen to
 * @param {String}            [selector] A CSS DOM Element selector string to bind the listener to child elements matching the selector
 * @param {Function}          [handler] The callback function
 * @param {Object}            [args...] Additional arguments to pass to the callback function when triggered
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @sample Ink_Dom_Event_1_on.html 
 */

/**
 * Alias for `on` but will only be executed once.
 * bean.one() is an alias for bean.on() except that the handler will only be executed once and then removed for the event type(s).
 * http://github.com/fat/bean#one
 *
 * @method one
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to listen to
 * @param {String}            [selector] A CSS DOM Element selector string to bind the listener to child elements matching the selector
 * @param {Function}          [handler] The callback function
 * @param                     [args...] Additional arguments to pass to the callback function when triggered
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @sample Ink_Dom_Event_1_one.html 
 */

/**
 * Removes event handlers.
 * bean.off() is how you get rid of handlers once you no longer want them active. It's also a good idea to call off on elements before you remove them from your DOM; this gives Bean a chance to clean up some things and prevents memory leaks.
 * http://github.com/fat/bean#off
 *
 * @method off
 * @param {DOMElement|Object} element An HTML DOM element or any JavaScript Object
 * @param {String}            eventType An Event (or multiple events, space separated) to remove
 * @param {Function}          [handler] The specific callback function to remove
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @sample Ink_Dom_Event_1_off.html 
 */

/**
 * Clones events from one object to another
 * bean.clone() is a method for cloning events from one DOM element or object to another.
 * http://github.com/fat/bean#clone
 *
 * @method clone
 * @param {DOMElement|Object} destElement An HTML DOM element or any JavaScript Object to copy events to
 * @param {String}            srcElement An HTML DOM element or any JavaScript Object to copy events from
 * @param {String}            [eventType] An Event (or multiple events, space separated) to clone
 * 
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @sample Ink_Dom_Event_1_clone.html 
 */

/**
 * Triggers events.
 * http://github.com/fat/bean#fire
 *
 * @method fire
 * @param {DOMElement|Object} destElement An HTML DOM element or any JavaScript Object fire the event on
 * @param {String}            eventType An Event (or multiple events, space separated) to fire
 * @param                     [args...] Additional arguments to pass to the callback function when triggered
 *
 * @return {DOMElement|Object} Returns the original DOM Element or Javascript Object
 * @sample Ink_Dom_Event_1_fire.html 
 */

return Ink.extendObj(InkEvent, bean);

});
